/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.projects.toolbox_basics_project.spring.manager.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.transaction.annotation.Transactional;

import com.genia.toolbox.basics.bean.MutablePair;
import com.genia.toolbox.basics.bean.impl.PairImpl;
import com.genia.toolbox.persistence.bean.Persistable;
import com.genia.toolbox.persistence.criteria.Criteria;
import com.genia.toolbox.persistence.criteria.CriteriaResult;
import com.genia.toolbox.persistence.criteria.CriteriaType;
import com.genia.toolbox.persistence.dao.AbstractDAO;
import com.genia.toolbox.persistence.exception.PersistenceException;
import com.genia.toolbox.persistence.spring.configurator.PersistableConfigurator;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.BasicOrdered;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.OrderedManager;
import com.genia.toolbox.spring.provider.init.bean.SpringInitializable;

/**
 * implementation of {@link OrderedManager}.
 */
public class OrderedManagerImpl
    extends AbstractDAO
    implements OrderedManager, SpringInitializable
{

  /**
   * the <code>Log</code> variabe.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(OrderedManagerImpl.class);

  /**
   * map that keep the link beetween a real class and its first super-class that
   * is {@link Ordered} and {@link Persistable}.
   */
  private static final Map<Class<? extends Ordered>, Class<? extends Ordered>> BASE_CLASSES = new HashMap<Class<? extends Ordered>, Class<? extends Ordered>>();

  /**
   * map that keep the link beetween a class and the maximal and minimal values
   * for the order property.
   */
  private static final Map<Class<? extends Ordered>, MutablePair<Integer, Integer>> MIN_MAX_VALUES = new HashMap<Class<? extends Ordered>, MutablePair<Integer, Integer>>();

  /**
   * The list of {@link PersistableConfigurator} that contains the mapping files
   * to register.
   */
  private List<PersistableConfigurator> persistableConfigurator;



  /**
   * returns a {@link MutablePair} containing, in this order, the next minimal
   * and next maximal value for the order property of a class. This object is
   * shared, changing its value will be reflected to any other instance using
   * it. For this reason, reading and writing this object must be done within a
   * synchronised block.
   * 
   * @param objectClass
   *          the class of the object to inspect
   * @return a {@link MutablePair} containing, in this order, the next minimal
   *         and next maximal value for the order property of a class
   * @throws PersistenceException
   *           if an error occured
   */
  @SuppressWarnings("unchecked")
  private MutablePair<Integer, Integer> getMinMaxValueForClass(Class<? extends Ordered> objectClass)
      throws PersistenceException
  {
    synchronized (BASE_CLASSES) {
      Class<? extends Ordered> baseClass = BASE_CLASSES.get(objectClass);
      if (baseClass == null) {
        baseClass = objectClass;
        while (isPersistableAndOrdered(baseClass.getSuperclass())) {
          baseClass = (Class<? extends Ordered>) baseClass.getSuperclass();
        }
        BASE_CLASSES.put(objectClass, baseClass);
      }
      MutablePair<Integer, Integer> res = MIN_MAX_VALUES.get(baseClass);
      if (res == null) {
        res = computeMinMaxValueForClass(baseClass);
        MIN_MAX_VALUES.put(baseClass, res);
      }
      return res;
    }
  }



  /**
   * compute a {@link MutablePair} containing, in this order, the next minimal
   * and next maximal value for the order property of a class.
   * 
   * @param objectClass
   *          the class of the object to inspect
   * @return a {@link MutablePair} containing, in this order, the next minimal
   *         and next maximal value for the order property of a class
   * @throws PersistenceException
   *           if an error occured
   */
  @Transactional(readOnly = true)
  private MutablePair<Integer, Integer> computeMinMaxValueForClass(Class<? extends Ordered> objectClass)
      throws PersistenceException
  {
    MutablePair<Integer, Integer> pairResult = new PairImpl<Integer, Integer>();
    Criteria<? extends Ordered> baseCriteria = getCriteria(objectClass);
    Criteria<Number> projectedCriteria = baseCriteria.project(Number.class, min(getPropertyField(BasicOrdered.NAME_ORDER)));
    CriteriaType<Number> maxCriteriaType = projectedCriteria.addProjection(Number.class, max(getPropertyField(BasicOrdered.NAME_ORDER)));
    CriteriaResult<Number> result = findUnique(projectedCriteria);
    if (result == null) {
      pairResult.setFirstElement(-1);
      pairResult.setSecondElement(1);
    }
    else {
      pairResult.setFirstElement(result.get() == null ? -1 : result.get().intValue() - 1);
      pairResult.setSecondElement(result.get(maxCriteriaType) == null ? 1 : result.get(maxCriteriaType).intValue() + 1);
    }
    return pairResult;
  }



  /**
   * returns whether a {@link Class} implements {@link Ordered} and
   * {@link Persistable}.
   * 
   * @param objectClass
   *          the {@link Class} to inspect
   * @return whether a {@link Class} implements {@link Ordered} and
   *         {@link Persistable}
   */
  private boolean isPersistableAndOrdered(Class<?> objectClass)
  {
    return Ordered.class.isAssignableFrom(objectClass) && Persistable.class.isAssignableFrom(objectClass);
  }



  /**
   * returns the order to give to an object for it to have the maximal order.
   * 
   * @param objectClass
   *          the class of the object
   * @return the order to give to an object for it to have the maximal order *
   * @throws PersistenceException
   *           when an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.OrderedManager#getNewMaxOrder(java.lang.Class)
   */
  public int getNewMaxOrder(Class<? extends Ordered> objectClass)
      throws PersistenceException
  {
    MutablePair<Integer, Integer> minMaxValue = getMinMaxValueForClass(objectClass);
    synchronized (minMaxValue) {
      int value = minMaxValue.getSecondElement();
      minMaxValue.setSecondElement(value + 1);
      return value;
    }
  }



  /**
   * returns the order to give to an object for it to have the minimal order.
   * 
   * @param objectClass
   *          the class of the object
   * @return the order to give to an object for it to have the minimal order
   * @throws PersistenceException
   *           when an error occured
   * @see com.genia.toolbox.projects.toolbox_basics_project.spring.manager.OrderedManager#getNewMinOrder(java.lang.Class)
   */
  public int getNewMinOrder(Class<? extends Ordered> objectClass)
      throws PersistenceException
  {
    MutablePair<Integer, Integer> minMaxValue = getMinMaxValueForClass(objectClass);
    synchronized (minMaxValue) {
      int value = minMaxValue.getFirstElement();
      minMaxValue.setFirstElement(value - 1);
      return value;
    }
  }



  /**
   * getter for the order property. This property allow to sort the
   * <code>SpringInitializable</code>.
   * 
   * @return the order
   * @see com.genia.toolbox.spring.provider.init.bean.SpringInitializable#getOrder()
   */
  public int getOrder()
  {
    return 0;
  }



  /**
   * The method to be called at the start of the application.
   * 
   * @see com.genia.toolbox.spring.provider.init.bean.SpringInitializable#init()
   */
  @SuppressWarnings("unchecked")
  @Transactional(readOnly = true)
  public void init()
  {
    for (PersistableConfigurator persistableConfigurator : getPersistableConfigurator()) {
      for (Class<? extends Persistable<?>> objectClass : persistableConfigurator.getPersistentTypes()) {
        if (isPersistableAndOrdered(objectClass)) {
          try {
            getMinMaxValueForClass((Class<? extends Ordered>) objectClass);
          }
          catch (PersistenceException e) {
            LOGGER.error(e.getMessage(), e);
          }
        }
      }
    }
  }



  /**
   * getter for the persistableConfigurator property.
   * 
   * @return the persistableConfigurator
   */
  public List<PersistableConfigurator> getPersistableConfigurator()
  {
    return persistableConfigurator;
  }



  /**
   * setter for the persistableConfigurator property.
   * 
   * @param persistableConfigurator
   *          the persistableConfigurator to set
   */
  public void setPersistableConfigurator(List<PersistableConfigurator> persistableConfigurator)
  {
    this.persistableConfigurator = persistableConfigurator;
  }

}
